﻿using System;
using System.Linq;
using System.Text;
using System.IO;

namespace Yaml.Presentation.Parsing {
    public class Reader {
        /// <summary>
        /// The text we are going to scan
        /// </summary>
        private TextReader reader;
        private StringBuilder buffer;
        private int index;
        private Encoding encoding;

        /// <summary>
        /// Create a new reader from a StreamReader, extracting from it the Encoding.
        /// </summary>
        public Reader(StreamReader reader) {
            Initialize(reader);
            encoding = reader.CurrentEncoding;
        }

        /// <summary>
        /// Create a new reader from a string, assumes Unicode encoding
        /// </summary>
        /// <param name="input"></param>
        public Reader(string input) : this(new StringReader(input), Encoding.Unicode) {
        }

        /// <summary>
        /// Create a new reader from a generic TextReader, specifying the encoding directly.
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="encoding"></param>
        public Reader(TextReader reader, Encoding encoding) {
            Initialize(reader);
            // We have to set the encoding specifically here.
            this.encoding = encoding;
        }

        private void Initialize(TextReader reader) {
            this.index = 0;
            this.buffer = new StringBuilder();
            this.reader = reader;
            Read(1);
        }

        /// <summary>
        /// The current read position of the reader.
        /// This is always less that or equal to the number of characters that have been read from the underlying "stream"
        /// </summary>
        public int Index { get { return index; } }
        /// <summary>
        /// The current encoding that is being used.
        /// </summary>
        public Encoding Encoding { get { return encoding; } }
        /// <summary>
        /// Have we met the end of the "stream"
        /// </summary>
        public bool AtEnd { get { return buffer[index] == '\0'; } }

        /// <summary>
        /// Take a peek at the character at the current position.
        /// </summary>
        /// <returns></returns>
        public char Peek() {
            return buffer[index];
        }

        /// <summary>
        /// Take a peek at the character at the position specified relative to the current position.
        /// This can be position or negative and if outside the bounds of the stream just returns '\0'
        /// </summary>
        public char PeekRelative(int relativeIndex) {
            Ensure(relativeIndex);
            if (index + relativeIndex < 0 || index + relativeIndex > buffer.Length) {
                return '\0';
            }
            return buffer[index + relativeIndex];
        }

        /// <summary>
        /// Take a peek at the character at the absolute position specified.
        /// If outside the bounds of the stream just returns '\0'
        /// </summary>
        public char Peek(int absoluteIndex) {
            return PeekRelative(absoluteIndex - index);
        }

        public string Prefix(int length) {
            Utilities.Requires.That(length>0, "length must be greater than zero", "length");
            Ensure(length);
            if (index + length > buffer.Length) {
                return buffer.ToString(index, buffer.Length - index);
            } else {
                return buffer.ToString(index, length);
            }
        }

        /// <summary>
        /// Move the current position forward 1 character
        /// </summary>
        public void Forward() {
            Forward(1);
        }

        /// <summary>
        /// Move the current position forward the specified number of characters
        /// </summary>
        public void Forward(int charCount) {
            Ensure(charCount);
            index = buffer.Length - 1;
        }

        public char Read() {
            char ch = Peek();
            Ensure(1);
            index++;
            if (index >= buffer.Length) {
                index = buffer.Length - 1;
            }
            return ch;
        }

        /// <summary>
        /// Read the specified number of characters, moving the current position accordingly
        /// </summary>
        public string Read(int charCount) {
            Ensure(charCount);
            string str = buffer.ToString(index, buffer.Length - index);
            index += charCount;
            if (index >= buffer.Length) {
                index = buffer.Length - 1;
            }
            return str;
        }

        /// <summary>
        /// Get the string spanning the specified start and end values
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public string GetSpan(int start, int end) {
            return buffer.ToString(start, end - start);
        }

        private void Ensure(int charCount) {
            if (charCount > 0) {
                char[] buff = new char[charCount];
                int count = reader.ReadBlock(buff, 0, charCount);
                buffer.Append(buff, 0, count);
                if (count < charCount) {
                    // We have reached the end of the reader
                    buffer.Append('\0');
                }
            }
        }
    }
}
